﻿using System;
using System.Globalization;
using System.Reflection;

namespace UnitDriven
{
	public class AsyncAsserter
	{
		private object sync = new object();
		public event EventHandler<AsyncAssertCompleteEventArgs> Complete;

		private void OnComplete(Exception exception)
		{
			OnComplete(TestResult.Fail, exception);
		}

		private void OnComplete(TestResult status)
		{
			OnComplete(status, null);
		}

		protected virtual void OnComplete(TestResult status, Exception exception)
		{
			lock (sync)
			{
				if (Complete != null)
				{
					Complete(this, new AsyncAssertCompleteEventArgs(status, exception));
					Complete = null;
				}
			}
		}

		public virtual void Success()
		{
			OnComplete(TestResult.Success);
		}

		public virtual void Indeterminate()
		{
			OnComplete(TestResult.Indeterminate);
		}

		public void Fail()
		{
            Fail(Properties.Resources.AsyncAssertFail);
		}

		public void Fail(Exception ex)
		{
			OnComplete(TestResult.Fail, ex);
		}

		public void Fail(string message, params object[] args)
		{
			OnComplete(TestResult.Fail, new TestException(string.Format(CultureInfo.CurrentCulture, message, args)));
		}

		public void IsNotNull(object actual)
		{
            IsNotNull(actual, Properties.Resources.AsyncAssertIsNotNullFailed);
		}

		public void IsNotNull(object actual, string message, params object[] args)
		{
			if (actual == null)
				OnComplete(new TestException(string.Format(CultureInfo.CurrentCulture, message, args)));
		}

		public void IsNull(object actual)
		{
            IsNull(actual, Properties.Resources.AsyncAssertIsNullFailed, actual.ToDisplay());
		}

		public void IsNull(object actual, string message, params object[] args)
		{
			if (actual != null)
				OnComplete(new TestException(string.Format(CultureInfo.CurrentCulture, message, args)));
		}

		public void AreEqual(object expected, object actual)
		{
			if (expected == null)
				throw new ArgumentNullException("expected");

            AreEqual(expected, actual, Properties.Resources.AsyncAssertAreEqualFailed, expected.ToDisplay(), actual.ToDisplay());
		}

		public void AreEqual(object expected, object actual, string message, params object[] args)
		{
			if (!object.Equals(expected, actual))
			{
				OnComplete(new TestException(string.Format(CultureInfo.CurrentCulture, message, args)));
			}
		}

		public void AreSame(object expected, object actual)
		{
			if (expected == null)
				throw new ArgumentNullException("expected");

            AreSame(expected, actual, Properties.Resources.AsyncAssertAreSameFailed, expected.ToDisplay(), actual.ToDisplay());
		}

		public void AreSame(object expected, object actual, string message, params object[] args)
		{
			if (expected != actual)
				OnComplete(new TestException(string.Format(CultureInfo.CurrentCulture, message, args)));
		}

		public void IsFalse(bool actual)
		{
            IsFalse(actual, Properties.Resources.AsyncAssertIsFalseFailed);
		}

		public void IsFalse(bool actual, string message, params object[] args)
		{
			if (actual)
				OnComplete(new TestException(string.Format(CultureInfo.CurrentCulture, message, args)));
		}

		public void IsTrue(bool actual)
		{
            IsTrue(actual, Properties.Resources.AsyncAssertIsTrueFailed);
		}

		public void IsTrue(bool actual, string message, params object[] args)
		{
			if (!actual)
				OnComplete(new TestException(string.Format(CultureInfo.CurrentCulture, message, args)));
		}

		public void Throws(Action action)
		{
			this.Throws(action, null);
		}

		public void Throws(Action action, Type exceptionType)
		{
			try
			{
				action.Invoke();
			}
			catch (Exception ex)
			{
#if NETFX_CORE
                if(exceptionType == null || !exceptionType.GetTypeInfo().IsAssignableFrom(ex.GetType().GetTypeInfo()))
#else
				if (exceptionType == null || !exceptionType.IsAssignableFrom(ex.GetType()))
#endif
					throw;
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Top level exception handler for background threads.")]
		public void Try(Action action)
		{
			if (action == null)
				throw new ArgumentNullException("action");

			try
			{
				action.Invoke();
			}
			catch (TargetInvocationException tiex)
			{
				OnComplete(tiex.InnerException ?? tiex);
			}
			catch (Exception ex)
			{
				OnComplete(ex);
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Top level exception handler for background threads.")]
		public object Try(Delegate method, params object[] arguments)
		{
			if (method == null)
				throw new ArgumentNullException("method");

			try
			{
				return method.DynamicInvoke(arguments);
			}
			catch (TargetInvocationException tiex)
			{
				OnComplete(tiex.InnerException ?? tiex);
			}
			catch (Exception ex)
			{
				OnComplete(ex);
			}

			return null;
		}
    }
}
